package com.cloudfast.dao.impl;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Session;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.transform.Transformers;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cloudfast.common.jdbc.reflect.ReflectUtils;
import com.cloudfast.common.page.Page;
import com.cloudfast.context.error.CloudException;
import com.cloudfast.context.error.ErrorCode;
import com.cloudfast.dao.BaseDao;
import com.cloudfast.entity.BaseEntity;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
 * @author liuyw
 * @描述:基础类
 * @date 2020年4月30日
 */
@SuppressWarnings("unchecked")
public class BaseDaoImpl<T extends BaseEntity, Key> implements BaseDao<T, Key> {

    private static final Logger logger = LoggerFactory.getLogger(Logger.class);

    @PersistenceContext
    private EntityManager entityManger;

    private Class<T> mEntityClass;

    // 逻辑删除 对应deleteField字段
    private Map<String, Object> logicalDeletionKeys;

    // 表名
    private String tableName;

    // 主键实体名
    private String primaryFieldKey;

    // 主键数据库名
    private String primaryColumnKey;

    private StringBuilder logicalDeletionSQL = new StringBuilder("delete from ");

    private ReflectUtils<T> reflectUtils = new ReflectUtils<>();

    // 查询用的字段
    private String selectColumns = null;

    // 是否有
    private boolean isSubTable = false;

    /**
     * 删除多对多
     */
    private List<String> deletes = null;
    /**
     * 多对多insert
     */
    private String insertMoreSQL = null;

    private Gson gson = null;

    @Override
    public EntityManager getEntityManager() {
        return entityManger;
    }

    public BaseDaoImpl() {
        gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
        Type genType = getClass().getGenericSuperclass();
        if (genType instanceof ParameterizedType) {
            Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
            mEntityClass = (Class<T>) params[0];
            init();
        }
    }

    public BaseDaoImpl(Class<T> mEntityClass) {
        this.mEntityClass = mEntityClass;
        init();
    }

    private void init() {
        // 获取 数据库table名称
        this.tableName = ReflectUtils.getTableName(mEntityClass);

        // 主键名称
        primaryFieldKey = ReflectUtils.getPrimaryKeyField(mEntityClass);
        primaryColumnKey = ReflectUtils.getPrimaryKeyColumn(mEntityClass);
        // 逻辑删除字段
        logicalDeletionKeys = ReflectUtils.getDeleteColumn(mEntityClass);
        // 逻辑删除处理
        if (logicalDeletionKeys != null) {
            logicalDeletionSQL = new StringBuilder();
            logicalDeletionSQL.append("update ").append(mEntityClass.getSimpleName());
            logicalDeletionSQL.append(" set  ");
            int i = 0;
            for (String key : logicalDeletionKeys.keySet()) {
                if (i > 0) {
                    logicalDeletionSQL.append(",");
                }
                logicalDeletionSQL.append(key).append(" = '1'");
                i++;
            }
        } else {
            logicalDeletionSQL.append(mEntityClass.getSimpleName());
        }
        // 是否有分表
        isSubTable = ReflectUtils.isSubTable(mEntityClass);
        selectColumns = ReflectUtils.getSelectColumns(mEntityClass);
        // 多对多
        deletes = ReflectUtils.getDeleteJoinTable(mEntityClass);
        insertMoreSQL = ReflectUtils.getInsertJoinTable(mEntityClass);
    }

    /**
     * 获取 SESSION
     */
    public Session getSession() {
        return (Session) getEntityManager().getDelegate();
    }

    @Override
    public int excute(String hql) {
        return excute(hql, null);
    }

    @Override
    public int nativeExcute(String sql) {
        return nativeExcute(sql, null);
    }

    @Override
    public int excute(String hql, Map<String, Object> params) {

        return this.createQuery(hql, params).executeUpdate();
    }

    @Override
    public int nativeExcute(String sql, Map<String, Object> params) {
        return this.createNativeQuery(sql, params).executeUpdate();
    }

    @Override
    public List<T> findList(String hql) {
        return findList(hql, null);
    }

    @Override
    public List<T> findList(String hql, Map<String, Object> params) {
        return createQuery(hql, params).getResultList();
    }

    @Override
    public List<Map<String, Object>> nativeQuery(String sql) {
        return nativeQuery(sql, null);
    }

    @Override
    public List<Map<String, Object>> nativeQuery(String sql, Map<String, Object> params) {
        Query query = createNativeQuery(sql, params);
        query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        return query.getResultList();
    }


    @Override
    public Page<T> findPage(Page<T> page, T entity) {
        Map<String, Object> params = reflectUtils.findEntityParams(entity);
        StringBuilder hql = new StringBuilder(" from ");
        hql.append(entity.getClass().getSimpleName());
        hql.append(" where 1=1 ");
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            hql.append(" and ").append(entry.getKey());
            hql.append(" = :").append(entry.getKey());
        }
        return findPage(page, hql.toString(), params);
    }

    @Override
    public Page<T> findPage(Page<T> page, String hql) {
        return findPage(page, hql, null);
    }

    @Override
    public Page<T> findPage(Page<T> page, String hql, Map<String, Object> params) {
        try {
            Query query = createQuery(hql, params);
            page.setTotal(query.getResultList().size());
            queryPage(query, page);
            page.setRows(query.getResultList());
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return page;
    }

    @Override
    public Page<Map<String, Object>> nativeMapPage(Page<Map<String, Object>> page, String sql) {
        return nativeMapPage(page, sql, null);
    }

    @Override
    public Page<Map<String, Object>> nativeMapPage(Page<Map<String, Object>> page, String sql,
                                                   Map<String, Object> params) {
        Query query = createNativeQuery(sql, params);
        query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        page.setTotal(findSqlTotal(sql, params));
        queryPage(query, page);
        page.setRows(query.getResultList());
        return page;
    }

    @Override
    public void saveOrUpdate(T entity) {
        try {
            Field f = entity.getClass().getDeclaredField(primaryFieldKey);
            f.setAccessible(true);
            String subTableName = tableName;
            boolean isNewAdd = true;
            if (isSubTable) {
                // 创建分表
                subTableName = reflectUtils.findSubTableName(entity);
                StringBuilder createTableSQL = new StringBuilder("CREATE TABLE  If Not Exists " + subTableName
                        + " SELECT * FROM " + subTableName + " WHERE 1=2 ");
                nativeExcute(createTableSQL.toString());
            }
            Object id = reflectUtils.getPrimaryKeyValue(entity);
            Map<String, Object> sqls = reflectUtils.repositoryHQL(entity, subTableName, primaryColumnKey);
            String sql = sqls.get("insert").toString();
            if (id != null && findOne((Key) id) != null) {
                sql = sqls.get("update").toString();
                isNewAdd = false;
            }
            logger.info("saveOrUpdate>>>SQL:" + sql);
            logger.info("saveOrUpdate>>>参数:" + sqls.get("params"));

            nativeExcute(sql, (Map<String, Object>) sqls.get("params"));
            if (isNewAdd) {
                T newEntity = findEntity(subTableName, sqls.get("where").toString(),
                        (Map<String, Object>) sqls.get("params"));
                Object newEntityKey = reflectUtils.getPrimaryKeyValue(newEntity);

                if (f.getType().getName().endsWith("Long")) {
                    id = Long.parseLong(newEntityKey.toString());
                } else if (f.getType().getName().endsWith("Integer")) {
                    id = Integer.parseInt(newEntityKey.toString());
                } else {
                    id = newEntityKey.toString();
                }
                f.set(entity, id);
            }
            if (deletes != null && deletes.size() > 0) {
                Map<String, Object> deleteParams = new LinkedHashMap<String, Object>();
                deleteParams.put("id", id);
                for (String str : deletes) {
                    nativeExcute(str, deleteParams);
                }
            }
            if (StringUtils.isNotEmpty(insertMoreSQL)) {
                List<Object> _insertsValues = reflectUtils.getJoinTable(entity);
                for (Object obj : _insertsValues) {
                    Map<String, Object> insertParams = new LinkedHashMap<String, Object>();
                    insertParams.put("id", id);
                    insertParams.put("id2", obj);
                    nativeExcute(insertMoreSQL, insertParams);
                }
            }
            // selfGrade 处理
            Object parentId = reflectUtils.getGradeParentId(entity);
            if (parentId != null && StringUtils.isNotEmpty(parentId + "")) {
                T parent = findOne((Key) parentId);
                Object oGrade = "-";
                if (parent != null) {
                    oGrade = reflectUtils.getParentGradeCode(parent);
                }
                Map<String, Object> fillEntity = reflectUtils.fillGradeCode(entity, oGrade.toString());
                if ((boolean) fillEntity.get("update")) {
                    saveOrUpdate((T) fillEntity.get("data"));
                }
            }

        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public void deleteById(Key id) {
        try {
            StringBuilder deleteSQL = new StringBuilder(logicalDeletionSQL);
            deleteSQL.append(" where 1=1 and ");
            deleteSQL.append(primaryFieldKey);
            deleteSQL.append(" = :").append(primaryFieldKey);
            Map<String, Object> params = new HashMap<>();
            params.put(primaryFieldKey, id);
            logger.info("SQL:{},参数:{}", deleteSQL.toString(), params);
            this.excute(deleteSQL.toString(), params);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    @Override
    public T findOne(Key id) {
        try {
            StringBuilder hql = new StringBuilder();
            hql.append("from ").append(mEntityClass.getSimpleName());
            hql.append(" where ").append(primaryFieldKey);
            hql.append(" = :").append(primaryFieldKey);
            Map<String, Object> params = new HashMap<>();
            params.put(primaryFieldKey, id);
            Query query = createQuery(hql.toString(), params);
            List<T> list = query.getResultList();
            logger.info("findOne id:{}", id);
            if (list != null && list.size() == 1) {
                return list.get(0);
            }
            new CloudException(ErrorCode.DATA_MORE);
        } catch (CloudException e) {
            new CloudException(e.getErrorCode());
        }
        return null;
    }

    @Override
    public Page<T> nativeQueryPage(Page<T> page, String sql) {
        return nativeQueryPage(page, sql, null);
    }

    @SuppressWarnings("rawtypes")
	@Override
    public Page<T> nativeQueryPage(Page<T> page, String sql, Map<String, Object> params) {
        Query query = createNativeQuery(sql, params);
        query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        page.setTotal(findSqlTotal(sql, params));
        queryPage(query, page);
        List resultData = query.getResultList();
        if (resultData != null && resultData.size() > 0) {
            Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
            JSONArray dataArray = new JSONArray(resultData);
            Type lisType = new TypeToken<LinkedList<T>>() {
            }.getType();
            List<T> rows = gson.fromJson(dataArray.toString(), lisType);
            page.setRows(rows);
        }
        return page;
    }

    @SuppressWarnings("hiding")
    @Override
    public <T> T gsonEntity(String sql, Map<String, Object> params, Class<T> clazz) {
        logger.info("SQL和条件:{},{}", sql, params);
        Query query = createNativeQuery(sql, params);
        query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        List<Map<String, Object>> list = query.getResultList();
        if (list != null && list.size() > 0) {
            return gson.fromJson((new JSONObject(list.get(0))).toString(), clazz);
        }
        return null;
    }

    /**
     * 填充参数
     *
     * @param query
     * @param params
     * @author liuyw
     */
    private void queryParams(Query query, Map<String, Object> params) {
        if (params != null) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                query.setParameter(entry.getKey(), entry.getValue());
            }
        }
    }

    /**
     * 创建hql 的query对象
     *
     * @param hql
     * @param params
     * @return
     * @author liuyw
     */
    private Query createQuery(String hql, Map<String, Object> params) {
        Query query = getEntityManager().createQuery(hql);
        queryParams(query, params);
        return query;
    }

    /**
     * 创建sql的query对象
     *
     * @param sqlString
     * @param params
     * @return
     * @author liuyw
     */
    private Query createNativeQuery(String sqlString, Map<String, Object> params) {
        Query query = entityManger.createNativeQuery(sqlString);
        queryParams(query, params);
        return query;
    }

    /**
     * 设置sql的分页
     *
     * @param page
     * @author liuyw
     */
    private void queryPage(Query query, Page<?> page) {
        query.setFirstResult((page.getPageNo() - 1) * page.getPageSize());
        query.setMaxResults(page.getPageSize());
    }

    /**
     * 获取分页的总数
     *
     * @param hql
     * @param params
     * @return
     */
    private int findSqlTotal(String hql, Map<String, Object> params) {
        StringBuilder total = new StringBuilder("select count(*) from ( ").append(hql);
        total.append(" ) a ");
        Query query = createNativeQuery(total.toString(), params);
        return Integer.parseInt(query.getSingleResult().toString());
    }

    /**
     * 根据条件查询实体信息
     *
     * @param tableName
     * @param where
     * @param params    参数
     * @return
     */
    private T findEntity(String tableName, String where, Map<String, Object> params) {
        StringBuilder sql = new StringBuilder("select ");
        sql.append(selectColumns).append(" from ").append(tableName);
        sql.append(" where ").append(where);
        logger.info("findEntity>>>SQL:" + sql.toString());
        logger.info("findEntity>>>参数:" + params);
        Query query = createNativeQuery(sql.toString(), params);
        query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        List<Map<String, Object>> list = query.getResultList();
        if (list != null && list.size() > 0) {
            return gson.fromJson(new JSONObject(list.get(0)).toString(), mEntityClass);
        }
        return null;
    }
}
